#include <math.h>
#include <algorithm>
#include "actor.h"
#include "global.h"
#include <stdio.h>

// TODO: List of things to do
// 5. Look at Greg's comments on r25

// Completed Tasks
// 1. change from 1-based index to 0-based index.
// 2. Change -1 to -INF
// 4. Change sort() to linear search
// 3. change rand() to Boost rand (Rohith will send by email)
//    Handling return value of 'no path' from getNumEdegesOnGeodesic()

extern Param param;

// comparison function for use with sort() function
bool compareUtilities(ACTOR_UTILITY i, ACTOR_UTILITY j) { return (i.utility > j.utility); }


// global function
// returns the utility of the actor specified by 'id', corresponding to the
// network specified by pNetwk
double getUtility(int id, const Network* pNetwk)
{
    double utility = 0;
    int numEdgesOnGeodesic = 0;

    // calculate the utility for this actor as a function of the benefits due to presence of paths to other actors
    // while considering the costs because of having to maintain edges.
    for( int i=0; i<param.N; i++ )
    {
        if( i != id )
        {
            numEdgesOnGeodesic = pNetwk->getNumEdgesOnGeodesic( id, i );

            if( numEdgesOnGeodesic != INT_MAX )
            {
                // consider the benefit of direct/indirect ties
                utility += pow( (param.delta), numEdgesOnGeodesic );

                // consider the cost of direct ties
                if( numEdgesOnGeodesic == 1 )
                    utility -= param.c;
            }
        }
    }

    return utility;
}

Actor::Actor() : state(AS_ACTIVE), id(id), pNetwork(NULL), timePassive(0)
{
}

Actor::Actor(int id, Network* pNetwork) : state(AS_ACTIVE), id(id), pNetwork(pNetwork), timePassive(0)
{
}

Actor::~Actor()
{
}

void Actor::init(int id, Network* pNetwork)
{
    this->id = id;

    // pointer to the underlying network
    this->pNetwork = pNetwork;

    // start with the active state
    state = AS_ACTIVE;

    // If this actor is currently in PASSIVE state, 'timePassive' indicates the the time at which 
    // the state of this actor was set to PASSIVE state.
    timePassive = 0;
}



// implements the JW model. Invokes uniAdd()/mutualAdd() and/or
// uniDelete()/mutualDelete(), depending on the experiment conditions
// Returns 0 if this actor did not change the network, 1 if this actor changed the network
int Actor::act()
{
    double baseUtility = 0;
    ACTOR_UTILITY chosenActorUtility;
    bool bNetworkChanged = false;

    // array for holding the utilities computed after (temporary) add/delete w.r.t. to each actor
    // double* utilities = new double[param.N];
    // vector<ACTOR_UTILITY> vecUtilities( param.N, ACTOR_UTILITY(0, 0) );
    vector<double> vecUtilities( param.N, 0 );

    // TODO: Ask Subramanian if he plans to implement 'simulation time' in his DES
    // TODO: Discuss about the best way to implement active/passive cycles. As Greg pointed out,
    // do we need to implement this at all?
    if( state == AS_PASSIVE )
    {
        // if current time is more than timePassive + ACTIVE_PASSIVE_DURATION, set the state to ACTIVE
        if( time(NULL) > timePassive + ACTOR_PASSIVE_DURATION )
        {
            state = AS_ACTIVE;
        }
        else // still in passive state
        {
            // printf("Actor (id: %d) is still in PASSIVE state\n", id);
            return bNetworkChanged;
        }
    }


    // calculate base utility
    baseUtility = getUtility( pNetwork );


    for( int i=0; i<param.N; i++ )
    {
        if( i != id )
        {
            if( pNetwork->isAdjacent( id, i ) )
            {
                // There is an edge between this actor and pActor. Delete it.

                if( (param.expt == EC_UA_UD) || (param.expt == EC_MA_UD) )
                    vecUtilities[i] = uniDelete(i);
                else if( (param.expt == EC_UA_MD) || (param.expt == EC_MA_MD) )
                    vecUtilities[i] = mutualDelete(i);
                else
                {
                    // TODO: Thrown an exception
                    printf("param.expt is of an unknown type!!\n");
                }
            }
            else
            {
                // There is no edge between this actor and pActor. Add it.

                if( (param.expt == EC_UA_UD) || (param.expt == EC_UA_MD) )
                    vecUtilities[i] = uniAdd(i);
                else if( (param.expt == EC_MA_UD) || (param.expt == EC_MA_MD) )
                    vecUtilities[i] = mutualAdd(i);
                else
                {
                    // TODO: Thrown an exception
                    printf("param.expt is of an unknown type!!\n");
                }
            }
        }
        else
        {
            // This actor has nothing to get out of adding/deleting edge to himself
            vecUtilities[id] = NEGATIVE_INFINITY;
        }
    }

    chosenActorUtility = getOptimalActorUtility( vecUtilities );


    // act!!!. This is what we we have struggled this long for!
    // add/remove link that gives the optimal utility. Don't do anything if there is no improvement in
    // the utility of 'this' actor.
    if( chosenActorUtility.utility > baseUtility )
    {
        if( pNetwork->isAdjacent( id, chosenActorUtility.actorId ) )
            pNetwork->remove( id, chosenActorUtility.actorId );
        else
            pNetwork->add( id, chosenActorUtility.actorId );

        bNetworkChanged = true;
    }

    // generate a random number between 1 and 100
    boost::uniform_int<> uni_dist(1, 100);
    boost::variate_generator<base_generator_type&, boost::uniform_int<> > boostRandGenerator(generator, uni_dist);

    // consider changing the state of this actor
    if( ((boostRandGenerator()%100)+1) > (ACTOR_ACTIVE_PROB * 100) )
    {
        state = AS_PASSIVE;
        timePassive = time(NULL);
    }

    return bNetworkChanged;
}

// returns the actor id
int Actor::getID() const
{
    return id;
}

// returns the utility of this actor corresponding to the
// network specified by pNetwk
double Actor::getUtility(const Network* pNetwk) const
{
    return ::getUtility( id, pNetwk );
}


// Returns the utility of this player when we add an edge from this actor
// to the actor 'destActorId', unilaterally.
double Actor::uniAdd( int destActorId )
{
    // the utility for this actor, after adding an edge from this
    // actor to the actor 'destActorId'.
    double utilityAfter = 0;

    // create a copy of the network. Make any changes only on this copy.
    Network* pNetWkCopy = new Network( *(this->pNetwork) );

    // add an edge between this actor and the actor 'destActorId'
    pNetWkCopy->add( id, destActorId );

    // Get the utility of this actor w.r.t. the network copy (after adding the edge)
    utilityAfter = getUtility( pNetWkCopy );

    delete pNetWkCopy;

    return utilityAfter;
}

// Returns the utility of this player when we delete the edge from this
// actor to the actor 'destActorId', unilaterally
double Actor::uniDelete( int destActorId )
{
    // the utility for this actor, after deleting the edge from this
    // actor to the actor 'destActorId'.
    double utilityAfter = 0;

    // create a copy of the network. Make any changes only on this copy.
    Network* pNetWkCopy = new Network( *(this->pNetwork) );

    // delete the node between this actor and the actor 'destActorId'
    pNetWkCopy->remove( id, destActorId );

    // TODO: Remove (added only debugging)
    pNetWkCopy->dumpNetwork("srinath.txt");

    // Get the utility of this actor w.r.t. the network copy (after deleting the edge)
    utilityAfter = getUtility( pNetWkCopy );

    delete pNetWkCopy;

    return utilityAfter;
}


// Returns the utility of this player when we add an edge from this actor to the actor 'destActorId',
// only if it is acceptable for the actor 'destActorId'
// Returns NEGATIVE_INFINITY if adding this edge is not mutually acceptable
double Actor::mutualAdd( int destActorId )
{
    // the utility for this actor, after adding an edge from this actor
    // to the actor 'destActorId'.
    double utilityAfter = 0;

    // utilities (before and after adding the edge) for the actor 'destActorId'
    double destUtilityBefore = 0, destUtilityAfter = 0;

    // is addition of edge from this actor to the actor 'destActorId',
    // acceptable to the actor 'destActorId'?
    bool bAddAcceptable = false;


    // create a copy of the network. Make any changes only on this copy.
    Network* pNetWkCopy = new Network( *pNetwork );


    //[
    // check if adding an edge from this actor to the actor 'destActorId', would be acceptable to
    // the actor 'destActorId'. This is done by checking the utility of the actor 'destActorId' before and
    // after  adding the edge (on a copy of the network). This addition would be
    // acceptable to the actor 'destActorId' only if destUtilityAfter >= destUtilityBefore
    destUtilityBefore = ::getUtility( destActorId, pNetwork );

    // add an edge between this actor and the actor 'destActorId'
    pNetWkCopy->add( id, destActorId );

    // get the utility after adding the edge
    destUtilityAfter = ::getUtility( destActorId, pNetWkCopy );

    // Is this edge addition acceptable to the actor 'destActorId'?
    bAddAcceptable = (destUtilityAfter >= destUtilityBefore)? true : false;
    //]


    // If this edge addition is acceptable to the actor 'destActorId', get the utility for this actor,
    // otherwise set the utility to this actor to NEGATIVE_INFINITY
    utilityAfter = (bAddAcceptable)? getUtility( pNetWkCopy ) : NEGATIVE_INFINITY;

    delete pNetWkCopy;

    return utilityAfter;
}


// Returns the utility of this actor when we delete the edge from this actor to the actor 'destActorId',
// only if it is acceptable for the actor 'destActorId'.
// Returns NEGATIVE_INFINITY if deleting this edge is not mutually acceptable
double Actor::mutualDelete( int destActorId )
{
    // the utility for this actor, after deleting the edge from this actor to the actor 'destActorId'.
    double utilityAfter = 0;

    // utilities (before and after deleting the edge) for the actor 'destActorId'
    double destUtilityBefore = 0, destUtilityAfter = 0;

    // is deletion of edge from this actor to the actor 'destActorId', acceptable to the actor 'destActorId'
    bool bDeleteAcceptable = false;


    // create a copy of the network. Make any changes only on this copy.
    Network* pNetWkCopy = new Network( *pNetwork );


    //[
    // check if deleting the edge from this actor to the actor 'destActorId', would be acceptable to
    // the actor 'destActorId'. This is done by checking the utility of the actor 'destActorId' before and
    // after deleting the edge (on a copy of the network). This deletion would be acceptable to the actor
    // 'destActorId' only if destUtilityAfter >= destUtilityBefore
    destUtilityBefore = ::getUtility( destActorId, pNetwork );

    // delete the edge between this actor and the actor 'destActorId'
    pNetWkCopy->remove( id, destActorId );

    // get the utility after deleting the edge
    destUtilityAfter = ::getUtility( destActorId, pNetWkCopy );

    // Is this edge deletion acceptable to the actor 'destActorId'?
    bDeleteAcceptable = (destUtilityAfter >= destUtilityBefore)? true : false;
    //]


    // If this edge deletion is acceptable to the actor 'destActorId', get the utility for this actor,
    // otherwise set the utility to this actor to NEGATIVE_INFINITY
    utilityAfter = (bDeleteAcceptable)? getUtility( pNetWkCopy ) : NEGATIVE_INFINITY;

    delete pNetWkCopy;

    return utilityAfter;
}

// Returns the id of the actor to which this actor, if it add/deletes an edge, obtains the optimal utility
// If there are more than one such actor, randomizes on these actors
ACTOR_UTILITY Actor::getOptimalActorUtility(const vector<double>& vecUtilities)
{
    double maxUtility = NEGATIVE_INFINITY;
    int actorIdOptimalUtility = 0;

    // find out the max utility
    for( unsigned int i=0; i<vecUtilities.size(); i++ )
    {
        if( vecUtilities[i] > maxUtility )
        {
            maxUtility = vecUtilities[i];
            actorIdOptimalUtility = i;
        }
    }

    // check how many utilities are equal to the max utility
    int numMaxUtilities = 0;
    for( unsigned int i=0; i<vecUtilities.size(); i++ )
    {
        if( isEqual( vecUtilities[i], maxUtility ) )
            numMaxUtilities++;
    }


    
    // If there are more than one actors adding/deleting an edge to/from which this actor 
    // gets the same optimal utility, randomize on these actors
    if( numMaxUtilities > 1 )
    {
        // generate a random number between 1 and numMaxUtilities
        boost::uniform_int<> uni_dist(1, numMaxUtilities);
        boost::variate_generator<base_generator_type&, boost::uniform_int<> > boostRandGenerator(generator, uni_dist);

        // int randomActorOrdinal = (rand() % numMaxUtilities) + 1;
        int randomActorOrdinal = (boostRandGenerator() % numMaxUtilities) + 1;

        // pick the actor at randomActorOrdinal.
        for( unsigned int i=0; i<vecUtilities.size(); i++ )
        {
            if( isEqual( vecUtilities[i], maxUtility ) )
                randomActorOrdinal--;

            if( randomActorOrdinal == 0 )
            {
                actorIdOptimalUtility = i;
                break;
            }
        }
    }

    return ACTOR_UTILITY(actorIdOptimalUtility, maxUtility);
}
